package com.googlecode.dex2jar.tools;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;

import com.googlecode.d2j.dex.writer.DexFileWriter;
import com.googlecode.d2j.jasmin.Jasmins;
import com.googlecode.d2j.map.*;
import com.googlecode.d2j.reader.DexFileReader;
import com.googlecode.d2j.reader.zip.ZipUtil;
import com.googlecode.d2j.smali.Smali;
import com.googlecode.d2j.visitors.DexFileVisitor;
import com.googlecode.dex2jar.tools.BaseCmd.Syntax;

@Syntax(cmd = "d2j-dex-mapping", syntax = "[options] <dex>", desc = "remap names in dex file")
public class DexRemapCmd extends BaseCmd {
    @Opt(opt = "o", longOpt = "output", description = "the name of the dex file that will be written. The default is out.dex", argName = "FILE")
    private File output;
    @Opt(opt = "L", longOpt = "library", description = "libraries we want load ',' separated")
    private String library;
    @Opt(opt = "C", longOpt = "config-file", description = "the config file generated by proguard", argName = "conf")
    private Path config;

    public static void main(String[] args) {
        new DexRemapCmd().doMain(args);
    }

    void V(String fmt, Object... args) {
        System.err.println(String.format(fmt, args));
    }

    @Override
    protected void doCommandLine() throws Exception {

        if (remainingArgs.length < 1) {
            System.err.println("ERROR: no file to process");
            return;
        }

        if (config == null) {
            System.err.println("ERROR: please set the config file -L,--config-file");
            return;
        }

        if (output == null) {
            output = new File("out.dex");
        }

        final InheritanceTree tree = buildRenameTree();

        DexFileWriter fw = new DexFileWriter();

        DexFileVisitor fv = new DexFileVisitor(fw) {
            @Override
            public void visitEnd() {
                //
            }
        };

        fv = new DexMappingAdapter(tree, fv);

        for (String s : remainingArgs) {
            V(">> Remap %s -> %s", s, output);
            DexFileReader reader = new DexFileReader(ZipUtil.readDex(new File(s)));
            reader.accept(fv);
        }
        fw.visitEnd();

        byte[] data = fw.toByteArray();
        Files.write(output.toPath(),data);
    }

    private InheritanceTree buildRenameTree() throws IOException {

        final InheritanceTree tree = new InheritanceTree();
        AutoDetectSourceProcess processer = new AutoDetectSourceProcess() {
            DexInheritanceFileVisitor dfv = new DexInheritanceFileVisitor(tree);
            AsmInheritanceClassVisitor acv = new AsmInheritanceClassVisitor(tree);

            @Override
            protected void onDex(Path file) throws IOException {
                DexFileReader r = new DexFileReader(ZipUtil.readDex(Files.readAllBytes(file)));
                r.accept(dfv, DexFileReader.SKIP_CODE | DexFileReader.SKIP_ANNOTATION
                        | DexFileReader.SKIP_FIELD_CONSTANT);
            }

            @Override
            protected void onClass(Path file) throws IOException {
                ClassReader cr = new ClassReader(Files.readAllBytes(file));
                cr.accept(acv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
            }

            @Override
            protected void onJasmin(Path file) throws IOException {
                ClassNode cn = Jasmins.parse(file);
                cn.accept(acv);
            }

            @Override
            protected void onSmali(Path file) throws IOException {
                Smali smali = new Smali();
                smali.smaliFile(file, dfv);
            }
        };

        for (String lib : library.split(",")) {
            V("<< Load Lib %s", lib);
            tree.updateFrom(lib, true);
            processer.process(lib);
        }
        for (String s : remainingArgs) {
            V("<< Load App %s", s);
            tree.updateFrom(s, false);
            processer.process(s);
        }
        V("<< Link for rename");
        tree.link();

        V("<< Apply config file %s", config);
        try (Reader configReadre = Files.newBufferedReader(config, StandardCharsets.UTF_8)) {
            new ProguardMappingParser().parse(configReadre, tree);
        }
        return tree;
    }
}
